/* Copyright (c) 2003 The Nutch Organization. All rights reserved. */ /* Use subject to the conditions in http://www.nutch.org/LICENSE.txt. */ package net.nutch.util; import java.lang.ref.SoftReference; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collections; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * A Map which uses {@link SoftReference}s to keep track of values. * * <p> * * This class is suitable for use as a best-effort caching mechanism. * Under the hood, it is simply a {@link HashMap}, which wraps it's * values with SoftReference</code>s. Unlike <code>HashMap</code>, * however, null values should not be used- they will be * indistinguisable from values which have been garbage collected. * * <p> * * Values should implement {@link FinalizationNotifier}, so keys can * be cleaned up as values are garbage collected. If values are added * which do not support this interface, the associated keys will * continue to be referenced by this data structure until explicitly * removed. * * <p> * * Expiration policy is determined by the JVM's implementation of * <code>SoftReference</code>. */ public class SoftHashMap extends AbstractMap implements Map { Map hashMap; ArrayList keysToDelete; /** * An interface for Object which accept notification when an another * Object is finalized. */ public interface FinalizationListener { /** * This method will be called when a {@link FinalizationNotifier} * this Object is registered with is being finalized. * <em>Note</em> that this method is not passed a reference to the * Object which is undergoing finalization, since creating a new * reference to that object may block finalization. */ public void finalizationOccurring(); } /** * An interface for a Objects which can notify an object when they * are finalized. Upon finalization, Objects which implement this * interface will call the <code>finalizationOccurring</code> method * of all {@link FinalizationListener}s that have registered with * it. */ public interface FinalizationNotifier { /** * Registers a {@link FinalizationListener} for this object. */ public void addFinalizationListener(FinalizationListener listener); } private class MyFinalizationListener implements FinalizationListener { Object key; MyFinalizationListener(Object key, FinalizationNotifier value) { this.key= key; value.addFinalizationListener(this); } public void finalizationOccurring() { SoftHashMap.this.queueKeyForDeletion(key); } } public SoftHashMap() { hashMap= Collections.synchronizedMap(new HashMap()); keysToDelete= new ArrayList(128); } public void clear() { hashMap.clear(); } // queues a key for deletion- called by FinalizationListener which // is listening for a value's expiration protected void queueKeyForDeletion(Object key) { synchronized (keysToDelete) { purgeQueuedKeys(); keysToDelete.add(key); } } // purges keys listed in keysToDelete from map protected void purgeQueuedKeys() { synchronized (keysToDelete) { for (int i= keysToDelete.size() - 1; i >= 0 ; i--) { remove(keysToDelete.get(i)); } keysToDelete.clear(); } } /** * Returns true if this map contains a mapping for the specified key. * * <em>Note</em> that this method can return true if the value has * been garbage collected, but the key has not been cleared. * Additionally, the finalizer may invalidate the result of this * operation before a subsequent <code>get()</code> can be issued. */ public boolean containsKey(Object key) { return hashMap.containsKey(key); } /** * Not Implemented * * <em>Note</em> that the finalizer may invalidate the result an * implementation would return. */ public boolean containsValue(Object value) throws UnsupportedOperationException { throw new UnsupportedOperationException("SoftHashMap.containsValue is " + "not implemented"); } /** * Not Implemented */ public Set entrySet() throws UnsupportedOperationException { throw new UnsupportedOperationException("SoftHashMap.entrySet() not implemented"); } public Object get(Object key) { SoftReference ref= (SoftReference) hashMap.get(key); if (ref == null) { return null; } return ref.get(); } public boolean isEmpty() { purgeQueuedKeys(); return hashMap.isEmpty(); } public Set keySet() { purgeQueuedKeys(); return hashMap.keySet(); } /** * Associates the specified value with the specified key in this * map. If the map previously contained a mapping for this key, the * old value is replaced. * * <p> * * <em>Note</em>: <code>value<code> must implemnt FinalizationNotifier * for keys to be freed properly when values are garbage collected. */ public Object put(Object key, Object value) { purgeQueuedKeys(); SoftReference oldRef= (SoftReference) hashMap.put(key, new SoftReference(value)); try { new MyFinalizationListener(key, (FinalizationNotifier) value); } catch (ClassCastException e) { // fixme: throw an exception? warn? } if (oldRef == null) return null; return oldRef.get(); } public Object remove(Object key) { SoftReference ref= (SoftReference) hashMap.remove(key); if (ref == null) return null; return ref.get(); } public int size() { purgeQueuedKeys(); return hashMap.size(); } /** * Not Implemented */ public Collection values() throws UnsupportedOperationException { throw new UnsupportedOperationException("SoftHashMap: values() not implemnted"); } }